#pragma once
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <set>
#include <thread>
using namespace std;
#include <unistd.h>
#include "serial.h"
#include "cJSON.h"
#include "queue.h"

struct m2_proto {
	int ctrl;
	string name;
	string id;
	uint8_t m2_id;
	set<string> pre, cur;

	map<int, string> mp_mode{
		{ 1, "设定制冷" },
		{ 2, "除湿" },
		{ 3, "清爽" },
		{ 4, "送风" },
		{ 5, "自动除湿" },
		{ 6, "贴心睡眠" },
		{ 8, "制热" },
		{ 9, "地暖" },

		{ 10, "强热 (地暖和制热同时开启)" },
	};

	map<int, string> mp_switch{
		{ 0, "关机" },
		{ 1, "开机" },
	};

	map<int, string> mp_wind{
		{ 0, "自动" },
		{ 1, "高速" },
		{ 2, "中速" },
		{ 3, "中高速" },
		/***/
		{ 4, "低速" },
		{ 5, "中低速" },
	};

	map<int, int> wind_aliyun_dev{
		{ 1, 4 },
		{ 2, 2 },
		{ 3, 1 },
	};

	map<int, int> wind_dev_aliyun{
		{ 4, 1 },
		{ 2, 2 },
		{ 1, 3 },
	};

	map<int, int> mode_aliyun_dev{
		{ 1, 1 },
		{ 2, 8 },
		{ 3, 4 },
		{ 4, 2 },
	};

	map<int, int> mode_dev_aliyun{
		{ 1, 1 },
		{ 8, 2 },
		{ 4, 3 },
		{ 2, 4 },
	};

	uint8_t calsum(const uint8_t *buf, int len)
	{
		uint8_t csum = 0;
		for (int i = 0; i < len; i++) {
			csum += buf[i];
		}
		return csum;
	}

	int scan(uint8_t *buf, int len)
	{
		uint8_t scan[] = { 0x01, 0x50, 0xFF, 0xFF, 0xFF, 0xFF, 0x4D };
		buf[3] = 0;
		scan[0] = m2_id;
		scan[sizeof(scan) - 1] = calsum(scan, sizeof(scan) - 1);

		if (write(ctrl, scan, sizeof(scan)) < 0) {
			cout << "write to device fail" << endl;
			return -1;
		}
		if ((len = read_port(ctrl, buf, len)) < 0) {
			cout << "read from device fail" << endl;
			return -1;
		} else if (len == 0) {
			cout << "read time out" << endl;
			return 1;
		}
		// dishex(buf, len, "scan msg:");
		if (buf[len - 1] == calsum(buf, len - 1)) {
			return 1;
		}
		return -1;
	}

	int analysis_scan_result(const uint8_t *buf, int len)
	{
		int cnt = buf[3];
		uint8_t op = 4;

		cJSON *scan_obj = cJSON_CreateObject();
		cJSON_AddStringToObject(scan_obj, "Active", "SCAN_ONLINE_RESULT");
		cJSON *obj = cJSON_AddObjectToObject(scan_obj, "Params");
		cJSON_AddStringToObject(obj, "Air_name", name.c_str());
		cJSON *dev_array = cJSON_AddArrayToObject(obj, "Devs");
		cJSON *show_array = cJSON_AddArrayToObject(obj, "Show");
		char dev_name[32] = { 0 };
		cur.clear();
		for (int i = 0; i < cnt; i++) {
			int oaddr = buf[op++];
			int iaddr = buf[op++];
			int sw = buf[op++];
			int temp = buf[op++];
			int mode = buf[op++];
			int wind = buf[op++];
			int room_temp = buf[op++];
			int err_code = buf[op++];
			op++;
			op++;

			cJSON *dev = cJSON_CreateObject();
			cJSON *show = cJSON_CreateObject();

			cJSON_AddNumberToObject(dev, "Outaddr", oaddr);
			cJSON_AddNumberToObject(dev, "Inaddr", iaddr);

			snprintf(dev_name, sizeof(dev_name), "%s%02X", name.c_str(), oaddr);
			cJSON_AddStringToObject(dev, "OutDevName", dev_name);
			snprintf(dev_name, sizeof(dev_name), "%s%02X%02X", name.c_str(), oaddr, iaddr);
			cJSON_AddStringToObject(dev, "InDevName", dev_name);

			cJSON_AddNumberToObject(dev, "Switch", sw);
			cJSON_AddNumberToObject(dev, "TargetTemperature", temp);
			cJSON_AddNumberToObject(dev, "WorkMode", mode_dev_aliyun[mode]);
			cJSON_AddNumberToObject(dev, "WindSpeed", wind_dev_aliyun[wind]);
			cJSON_AddNumberToObject(dev, "CurrentTemperature", room_temp);
			cJSON_AddNumberToObject(dev, "ErrorCode", err_code);
			cJSON_AddItemToArray(dev_array, dev);

			cJSON_AddStringToObject(show, "设备名称", dev_name);
			cJSON_AddStringToObject(show, "运行状态", mp_switch[sw].c_str());
			cJSON_AddStringToObject(show, "设定温度", to_string(temp).c_str());
			cJSON_AddStringToObject(show, "工作模式", (mp_mode[mode] + "-" + to_string(mode)).c_str());
			cJSON_AddStringToObject(show, "风速设置", (mp_wind[wind] + "-" + to_string(wind)).c_str());
			cJSON_AddStringToObject(show, "当前温度", to_string(room_temp).c_str());
			cJSON_AddItemToArray(show_array, show);
			cur.insert(dev_name);
		}
		if (cnt) {
			char *ptr = cJSON_Print(scan_obj);
			send_msg(CTRL_QUEUE, ptr, strlen(ptr));
			free(ptr);
		}
		cJSON_Delete(scan_obj);

		scan_obj = cJSON_CreateObject();
		cJSON_AddStringToObject(scan_obj, "Active", "SCAN_OFFLINE_RESULT");
		obj = cJSON_AddObjectToObject(scan_obj, "Params");
		cJSON_AddStringToObject(obj, "Air_name", name.c_str());
		dev_array = cJSON_AddArrayToObject(obj, "Devs");
		cnt = 0;
		for (auto it = pre.begin(); it != pre.end(); it++) {
			if (cur.find(*it) == cur.end()) {
				cJSON_AddItemToArray(dev_array, cJSON_CreateString((*it).c_str()));
				cnt++;
			}
		}
		if (cnt) {
			char *ptr = cJSON_Print(scan_obj);
			send_msg(CTRL_QUEUE, ptr, strlen(ptr));
			free(ptr);
		}
		cJSON_Delete(scan_obj);
		pre.swap(cur);
		return 0;
	}

	int dishex(const uint8_t *buf, int len, string msg)
	{
		cout << msg << name << endl;
		for (int i = 0; i < len; i++) {
			cout << hex << setw(2) << setfill('0') << (int)buf[i] << " ";
		}
		cout << endl;
		return 0;
	}

	int ctrl_dev(uint8_t func, uint8_t arg, uint8_t iaddr, uint8_t oaddr)
	{
		uint8_t cmd[256], pos = 1, checksum = 0;
		cmd[0] = m2_id;
		cmd[pos++] = func;
		cmd[pos++] = arg;
		cmd[pos++] = 0x01;
		cmd[pos++] = oaddr;
		cmd[pos++] = iaddr;
		cmd[pos] = calsum(cmd, pos);
		pos++;

		if (write(ctrl, cmd, pos) < 0) {
			return -1;
		}
		int len = read_port(ctrl, cmd, sizeof(cmd));
		return cmd[len - 1] == calsum(cmd, len - 1);
	}

	void ctrl_one_dev(const cJSON *dev)
	{
		cJSON *inaddr = cJSON_GetObjectItemCaseSensitive(dev, "Iaddr");
		cJSON *outaddr = cJSON_GetObjectItemCaseSensitive(dev, "Oaddr");
		cJSON *sw = cJSON_GetObjectItemCaseSensitive(dev, "Switch");
		cJSON *wind = cJSON_GetObjectItemCaseSensitive(dev, "WindSpeed");
		cJSON *temp = cJSON_GetObjectItemCaseSensitive(dev, "TargetTemperature");
		cJSON *mode = cJSON_GetObjectItemCaseSensitive(dev, "WorkMode");

		int op_sw = -1, op_wind = -1, op_temp = -1, op_mode = -1;

		if (cJSON_IsNumber(sw)) {
			op_sw = sw->valueint;
		}
		if (cJSON_IsNumber(wind)) {
			op_wind = wind->valueint;
		}
		if (cJSON_IsNumber(temp)) {
			op_temp = (uint8_t)temp->valuedouble;
		}
		if (cJSON_IsNumber(mode)) {
			op_mode = mode->valueint;
		}
		uint8_t iaddr = strtol(inaddr->valuestring, NULL, 10);
		uint8_t oaddr = strtol(outaddr->valuestring, NULL, 10);
		if (op_sw != -1) {
			ctrl_dev(0x31, op_sw, iaddr, oaddr);
		}
		if (op_mode != -1) {
			op_mode = mode_aliyun_dev[op_mode];
			ctrl_dev(0x33, op_mode, iaddr, oaddr);
		}
		if (op_temp != -1) {
			ctrl_dev(0x32, op_temp, iaddr, oaddr);
		}
		if (op_wind != -1) {
			op_wind = wind_aliyun_dev[op_wind];
			ctrl_dev(0x34, op_wind, iaddr, oaddr);
		}
		return;
	}

	int ctrl_multi_dev(const cJSON *devs)
	{
		cJSON *property = cJSON_GetObjectItemCaseSensitive(devs, "Property");
		cJSON *addrs = cJSON_GetObjectItemCaseSensitive(devs, "Addrs");
		cJSON *addr = NULL;
		uint8_t sw[512], wind[512], temp[512], mode[512];
		int sw_cnt = -1, wind_cnt = -1, temp_cnt = -1, mode_cnt = -1;
		int sw_op = 0, wind_op = 0, temp_op = 0, mode_op = 0;

		sw[0] = wind[0] = temp[0] = mode[0] = m2_id;
		cJSON *on = cJSON_GetObjectItemCaseSensitive(property, "Switch");
		if (cJSON_IsNumber(on)) {
			sw[1] = 0x31;
			sw[2] = on->valueint;
			sw_op = 1;
		}
		cJSON *jwind = cJSON_GetObjectItemCaseSensitive(property, "WindSpeed");
		if (cJSON_IsNull(jwind)) {
			wind[1] = 0x34;
			wind[2] = wind_aliyun_dev[jwind->valueint];
			wind_op = 1;
		}
		cJSON *jtemp = cJSON_GetObjectItemCaseSensitive(property, "TargetTemperature");
		if (cJSON_IsNumber(jtemp)) {
			temp[1] = 0x32;
			temp[2] = (uint8_t)jtemp->valuedouble;
			temp_op = 1;
		}
		cJSON *jmode = cJSON_GetObjectItemCaseSensitive(property, "WorkMode");
		if (cJSON_IsNumber(jmode)) {
			mode[1] = 0x33;
			mode[2] = mode_aliyun_dev[jmode->valueint];
			mode_op = 1;
		}

		uint8_t iaddr = 0;
		uint8_t oaddr = 0;
		uint8_t sw_checksum = 0, wind_checksum = 0, temp_checksum = 0, mode_checksum = 0;
		int offset = 4, cnt = 0, len = 0;
		cJSON_ArrayForEach(addr, addrs)
		{
			cJSON *inaddr = cJSON_GetObjectItemCaseSensitive(addr, "Iaddr");
			cJSON *outaddr = cJSON_GetObjectItemCaseSensitive(addr, "Oaddr");
			iaddr = strtol(inaddr->valuestring, NULL, 10);
			oaddr = strtol(outaddr->valuestring, NULL, 10);
			if (sw_op) {
				sw[offset] = oaddr;
				sw[offset + 1] = iaddr;
			}
			if (wind_op) {
				wind[offset] = oaddr;
				wind[offset + 1] = iaddr;
			}
			if (temp_op) {
				temp[offset] = oaddr;
				temp[offset + 1] = iaddr;
			}
			if (mode_op) {
				mode[offset] = oaddr;
				mode[offset + 1] = iaddr;
			}
			offset += 2;
			cnt++;
		}
		if (cnt) {
			sw[3] = wind[3] = temp[3] = mode[3] = cnt;
			for (int i = 0; i < offset; i++) {
				sw_checksum += sw[i];
				wind_checksum += wind[i];
				temp_checksum += temp[i];
				mode_checksum += mode[i];
			}
			sw[offset] = sw_checksum;
			wind[offset] = wind_checksum;
			temp[offset] = temp_checksum;
			mode[offset] = mode_checksum;
			offset++;
			if (sw_op) {
				if (write(ctrl, sw, offset) < 0) {
					return -1;
				}
				len = read_port(ctrl, sw, sizeof(sw));
			}
			if (wind_op) {
				if (write(ctrl, wind, offset) < 0) {
					return -1;
				}
				len = read_port(ctrl, wind, sizeof(wind));
			}
			if (temp_op) {
				if (write(ctrl, temp, offset) < 0) {
					return -1;
				}
				len = read_port(ctrl, temp, sizeof(temp));
			}
			if (mode_op) {
				if (write(ctrl, mode, offset) < 0) {
					return -1;
				}
				len = read_port(ctrl, mode, sizeof(mode));
			}
		}
		return 1;
	}

	void m2_msg_process(const char *msg)
	{
		cJSON *json = cJSON_Parse(msg);
		cJSON *act = cJSON_GetObjectItemCaseSensitive(json, "Active");
		cJSON *dev = cJSON_GetObjectItemCaseSensitive(json, "Devs");
		if (cJSON_IsString(act) && act->valuestring != NULL) {
			if ("CTRL_ONE_DEV" == string(act->valuestring)) {
				ctrl_one_dev(dev);
			} else if ("CTRL_MULTI_DEVS" == string(act->valuestring)) {
				ctrl_multi_dev(dev);
			}
		}
		cJSON_Delete(json);
	}

	static void monitor(m2_proto *self)
	{
		char buf[1024 * 8];
		string air_name = "/" + self->name + "_" + self->id;
		for (;;) {
			if (self->scan((uint8_t *)buf, sizeof(buf)) == 1) {
				self->analysis_scan_result((uint8_t *)buf, sizeof(buf));
			}
			if (timerecv_msg(air_name.c_str(), buf, sizeof(buf), 30) > 0) {
				cout << air_name << "recv:" << buf << endl;
				self->m2_msg_process(buf);
			}
		}
	}

	void start(const string &dev, const string &air_name, const string &air_id)
	{
		name = air_name;
		id = air_id;
		m2_id = strtol(id.c_str(), NULL, 10);
		ctrl = open_port(dev.c_str());
		if (ctrl < 0) {
			return;
		}
		thread thd(monitor, this);
		thd.detach();
	}
};